package com.qihoo.finance.fake.exporter;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.qihoo.finance.fake.entity.FakeEntity;
import com.qihoo.finance.fake.registry.FakeRegistryConfig;
import com.qihoo.finance.fake.service.FakeSourceService;
import com.qihoo.finance.fake.sql.SqlSessionConfiguration;
import com.qihoo.finance.fake.variety.VarietyService;
import com.qihoo.finance.fake.variety.VarietyServiceFactory;
import com.qihoo.finance.msf.api.domain.Response;
import com.qihoo.finance.msf.api.sys.facade.SystemDictFacade;
import com.qihoo.finance.msf.api.sys.facade.SystemParamFacade;
import com.qihoo.finance.msf.api.threadpool.facade.ThreadPoolFacade;
import com.qihoo.finance.msf.core.context.SpringContextHolder;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.common.utils.ReflectUtils;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.spring.boot.autoconfigure.DubboAutoConfiguration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@AutoConfigureAfter({DubboAutoConfiguration.class, FakeSourceService.class, SqlSessionConfiguration.class})
@Configuration
public class ExporterAutoConfiguration implements InitializingBean {
    private static Logger logger = LogManager.getLogger(ExporterAutoConfiguration.class);
    private static final ScheduledThreadPoolExecutor scheduledUnExportExecutor = new ScheduledThreadPoolExecutor(1,
            new ThreadFactoryBuilder().setNameFormat("fake-offline-%d").setDaemon(true).build());
    private static final ScheduledThreadPoolExecutor scheduledExportNewExecutor = new ScheduledThreadPoolExecutor(1,
            new ThreadFactoryBuilder().setNameFormat("fake-online-%d").setDaemon(true).build());
    private ConcurrentHashMap<String, ServiceConfig<Object>> exported = new ConcurrentHashMap<>();
    private final ApplicationConfig applicationConfig;
    private RegistryConfig registryConfig;
    private final ProtocolConfig protocolConfig;
    private final SpringContextHolder springContextHolder;
    private final FakeSourceService fakeSourceService;
    private final FakeRegistryConfig fakeRegistryConfig;

    public ExporterAutoConfiguration(ObjectProvider<ApplicationConfig> applicationConfigProvider
            , ObjectProvider<ProtocolConfig> protocolConfigConfigProvider
            , ObjectProvider<FakeSourceService> fakeSourceServiceProvider
            , ObjectProvider<SpringContextHolder> springContextHolderProvider
            , ObjectProvider<FakeRegistryConfig> fakeRegistryConfigProvider
            , ObjectProvider<RegistryConfig> registryConfigProvider
    ) {
        this.applicationConfig = applicationConfigProvider.getIfAvailable();
        this.protocolConfig = protocolConfigConfigProvider.getIfAvailable();
        this.springContextHolder = springContextHolderProvider.getIfAvailable();
        this.fakeSourceService = fakeSourceServiceProvider.getIfAvailable();
        this.fakeRegistryConfig = fakeRegistryConfigProvider.getIfAvailable();
        this.registryConfig = registryConfigProvider.getIfAvailable();
    }


    private static List<List<Object>> REAL_EXPORT_FACADE_ARRAY = new ArrayList<>();
    static {
        REAL_EXPORT_FACADE_ARRAY.add(Arrays.asList(ThreadPoolFacade.class, "threadPoolFacadeImpl"));
        REAL_EXPORT_FACADE_ARRAY.add(Arrays.asList(SystemDictFacade.class, "systemDictFacadeImpl"));
        REAL_EXPORT_FACADE_ARRAY.add(Arrays.asList(SystemParamFacade.class, "systemParamFacadeImpl"));
    }

    @Override
    public void afterPropertiesSet() {
        if (Objects.nonNull(fakeRegistryConfig)) {
            registryConfig = convertFakeRegistryConfig();
            for(List<Object> tuple: REAL_EXPORT_FACADE_ARRAY){
                Class<?> intf = (Class<?>) tuple.get(0);
                Object ref = SpringContextHolder.getBean(String.valueOf(tuple.get(1)));
                exportRealFacade(intf, intf.getName(), ref);
            }
        }
        logger.info("fake registry address: {}", registryConfig.getAddress());
        exportFakes(fakeSourceService.loadAll());
        scheduledUnExportExecutor.scheduleAtFixedRate(this::unExportFakes, 2, 1, TimeUnit.MINUTES);
        scheduledExportNewExecutor.scheduleAtFixedRate(this::exportNewFakes, 2, 1, TimeUnit.MINUTES);
    }

    private void exportNewFakes() {
        exportFakes(fakeSourceService.loadNewFakeList());
    }

    private void exportFakes(List<FakeEntity> list) {
        logger.info("load fake list size : {}", Objects.isNull(list) ? 0 : list.size());
        for (FakeEntity fakeEntity : list) {
            exportOne(fakeEntity);
        }
    }

    private int exportOne(FakeEntity fakeEntity) {
        try {
            Class<?> clazz = ReflectUtils.forName(fakeEntity.getInterfaceName());
            // 接口会重复但方法不会,所以只需要export一次接口即可
            int i = exportFake(clazz, fakeEntity.getInterfaceName());
            if (i != 0) {
                if (fakeEntity.getInterfaceState() != 1) {
                    fakeSourceService.updateState(fakeEntity, 1);
                }
            }
            return 1;
        } catch (Exception e) {
            logger.error("load Fake Exception, {}", fakeEntity, e);
        }
        return 0;
    }

    private void unExportFakes() {
        List<FakeEntity> list = fakeSourceService.loadOfflineFakes();
        for (FakeEntity fakeEntity : list) {
            try {
                ServiceConfig<Object> service = exported.remove(fakeEntity.getInterfaceName());
                if (Objects.nonNull(service)) {
                    service.unexport();
                }
                fakeSourceService.updateState(fakeEntity, 4);
                logger.info("fake unexport interface {} success", fakeEntity.getInterfaceName());
            } catch (Exception e) {
                logger.error(" fake unexport interface {} exception", fakeEntity.getInterfaceName(), e);
            }
        }
    }

    private int exportFake(Class<?> interfaceClass, String interfaceName) {
        try {
            if (exported.containsKey(interfaceName)) {
                return 1;
            }
            // 此实例很重，封装了与注册中心的连接，请自行缓存，否则可能造成内存和连接泄漏
            ServiceConfig<Object> service = new ServiceConfig<>();
            service.setApplication(applicationConfig);
            service.setRegistry(registryConfig);
            service.setProtocol(protocolConfig);
            service.setWeight(1);
            Object ref = java.lang.reflect.Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    String methodName = method.getName();
                    FakeEntity fakeEntity = SpringContextHolder.getBean(FakeSourceService.class).queryFakeOne(interfaceName, methodName);
                    if (Objects.isNull(fakeEntity)) {
                        logger.info("未加载到{}#{}对应的fake配置", interfaceName, methodName);
                        return null;
                    }
                    logger.info("获取fake配置完成, 接口:{}, 方法:{}, 默认配置:{}, {}", interfaceName, methodName, fakeEntity.getVarietyType(), fakeEntity.getVarietyValue());
                    logger.info("fake获取客户端ip: {}", RpcContext.getContext().getRemoteHost());
                    String type = StringUtils.defaultIfBlank(SpringContextHolder.getBean(FakeSourceService.class).queryFakeVarietyTypeByIp(RpcContext.getContext().getRemoteHost()), fakeEntity.getVarietyType());
                    logger.info("fake类型: {}", type);
                    VarietyService varietyService = VarietyServiceFactory.getVarietyServiceByType(type);
                    if (Objects.isNull(varietyService)) {
                        logger.info("未加载到对应的fake实现: {}", type);
                        return new Response().fail("ERROR", "Unsupported Fake Variety");
                    }

                    try{
                        Object obj = varietyService.populate(interfaceName, method, fakeEntity.getVarietyValue());
                        return obj;
                    } catch (Exception e){
                        logger.error(e);
                    }
                    return null;
                }
            });
            service.setRef(ref);
            service.setInterface(interfaceClass);
            // 暴露及注册服务
            service.export();
            exported.put(interfaceName, service);
            logger.info("fake export interface {} success ", interfaceName);
        } catch (Exception e) {
            logger.error("fake export interface {} exception ", interfaceName, e);
            return 0;
        }
        return 1;
    }

    private <T> void exportRealFacade(Class<?> interfaceClass, String interfaceName, T ref){
        if (exported.containsKey(interfaceName)) {
            return ;
        }
        // 此实例很重，封装了与注册中心的连接，请自行缓存，否则可能造成内存和连接泄漏
        ServiceConfig<Object> service = new ServiceConfig<>();
        service.setApplication(applicationConfig);
        service.setRegistry(registryConfig);
        service.setProtocol(protocolConfig);
        service.setWeight(1);
        service.setRef(ref);
        service.setInterface(interfaceClass);
        // 暴露及注册服务
        service.export();
        exported.put(interfaceName, service);
    }

    private RegistryConfig convertFakeRegistryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setProtocol(fakeRegistryConfig.getProtocol());
        registryConfig.setAddress(fakeRegistryConfig.getAddress());
        registryConfig.setPort(fakeRegistryConfig.getPort());
        registryConfig.setUsername(fakeRegistryConfig.getUsername());
        registryConfig.setPassword(fakeRegistryConfig.getPassword());
        registryConfig.setWait(fakeRegistryConfig.getWait());
        registryConfig.setCheck(fakeRegistryConfig.isCheck());
        registryConfig.setFile(fakeRegistryConfig.getFile());
        registryConfig.setTransport(fakeRegistryConfig.getTransport());
        registryConfig.setTransporter(fakeRegistryConfig.getTransporter());
        registryConfig.setServer(fakeRegistryConfig.getServer());
        registryConfig.setClient(fakeRegistryConfig.getClient());
        registryConfig.setTimeout(fakeRegistryConfig.getTimeout());
        registryConfig.setSession(fakeRegistryConfig.getSession());
        registryConfig.setDynamic(fakeRegistryConfig.isDynamic());
        registryConfig.setRegister(fakeRegistryConfig.isRegister());
        registryConfig.setSubscribe(fakeRegistryConfig.isSubscribe());
        registryConfig.setCluster(fakeRegistryConfig.getCluster());
        registryConfig.setGroup(fakeRegistryConfig.getGroup());
        registryConfig.setVersion(fakeRegistryConfig.getVersion());
        registryConfig.setParameters(fakeRegistryConfig.getParameters());
        registryConfig.setDefault(fakeRegistryConfig.isDefault());
        registryConfig.setSimplified(fakeRegistryConfig.getSimplified());
        registryConfig.setExtraKeys(fakeRegistryConfig.getExtraKeys());
        registryConfig.setId(fakeRegistryConfig.getId());
        registryConfig.setPrefix(fakeRegistryConfig.getPrefix());
        return registryConfig;
    }
}
